home *** CD-ROM | disk | FTP | other *** search
/ OpenGL Superbible (2nd Edition) / OpenGL SuperBible e2.iso / tools / GLUT-3.7 / PROGS / ADVANCED / shadowfun.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-08-12  |  30.5 KB  |  1,164 lines

  1.  
  2. /* Copyright (c) Mark J. Kilgard, 1997.  */
  3.  
  4. /* This program is freely distributable without licensing fees  and is
  5.    provided without guarantee or warrantee expressed or  implied. This
  6.    program is -not- in the public domain. */
  7.  
  8. /* Unix compile line:  cc -o shadowfun shadowfun.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm */
  9.  
  10. /* THIS PROGRAM REQUIRES GLU 1.2. If you have IRIX 5.3, you need patch 1449
  11.    (the IRIX 5.3 GLU 1.2 functionality patch) or its successor to use this
  12.    program.  GLU 1.2 is standard on IRIX 6.2 and later. */
  13.  
  14. /* This program demonstrates a light source and object of arbitrary geometry
  15.    casing a shadow on arbitary geometry.  The program uses OpenGL's feedback, 
  16.    stencil, and boundary tessellation support. */
  17.  
  18. #include <assert.h>
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <math.h>
  22. #include <GL/glut.h>
  23.  
  24. #ifdef GLU_VERSION_1_2
  25.  
  26. /* Win32 calling conventions. */
  27. #ifndef CALLBACK
  28. #define CALLBACK
  29. #endif
  30.  
  31. /* Some <math.h> files do not define M_PI... */
  32. #ifndef M_PI
  33. #define M_PI 3.14159265358979323846
  34. #endif
  35.  
  36. const float uniquePassThroughValue = 34567.0;
  37.  
  38. #define SmallerOf(a,b) ((a) < (b) ? (a) : (b))
  39.  
  40. int stencilBits;
  41.  
  42. /* Display list names. */
  43. enum {
  44.   /* Display lists should start at 1, not 0. */
  45.   DL_BALL = 1, DL_CONE, DL_LIGHT, DL_SHADOW_VOLUME, DL_SPHERE,
  46.   DL_ICO, DL_TORUS, DL_CUBE, DL_SHADOW_VOLUME_TOP, DL_BASE_SHADOW_VOLUME
  47. };
  48.  
  49. /* Menu option names. */
  50. enum {
  51.   /* Important for objectMaxRadius array that the shape enums appear first in
  52.      this list. */
  53.   M_TORUS, M_CUBE, M_SPHERE, M_ICO, M_DOUBLE_TORUS, M_ANGLE, M_BOUNDARY,
  54.   M_NO_SHADOW, M_NO_LIGHT, M_FRONT_VOLUME, M_BACK_VOLUME, M_SHADOW,
  55.   M_LIGHT_SOURCE_VIEW, M_NORMAL_VIEW, M_SPIN, M_SWING, M_STOP
  56. };
  57.  
  58. /* Coordinates. */
  59. enum {
  60.   X, Y, Z
  61. };
  62.  
  63. const int TEXDIM = 64;
  64.  
  65. int shape;
  66. GLfloat maxRadius;
  67. int renderMode = M_SHADOW;
  68. int view = M_NORMAL_VIEW;
  69. int renderBoundary = 0;
  70. GLfloat angle = 0.0;
  71. int frontFace = 1;
  72. int rotatingObject = 1;
  73. int swingingLight = 1;
  74. float swingTime = M_PI / 2.0;
  75.  
  76. GLfloat lightDiffuse[4] =
  77. {1.0, 0.0, 0.0, 1.0};
  78. GLfloat lightPos[4] =
  79. {60.0, 50.0, -350.0, 1.0};
  80. GLfloat objectPos[4] =
  81. {40.0, 30.0, -360.0, 1.0};
  82. GLfloat sceneEyePos[4] =
  83. {0.0, 0.0, 0.0, 0.0};
  84.  
  85. struct VertexHolder {
  86.   struct VertexHolder *next;
  87.   GLfloat v[2];
  88. };
  89.  
  90. typedef struct _ShadowVolumeMemoryPool {
  91.   /* Reference count because ShadowVolumeMemoryPool's can be shared between
  92.      multiple ShadowVolumeState's. */
  93.   int refcnt;
  94.  
  95.   GLUtesselator *tess;
  96.   GLfloat viewScale;
  97.  
  98.   /* Memory used for GLU tessellator combine callbacks. */
  99.   GLfloat *combineList;
  100.   int combineListSize;
  101.   int combineNext;
  102.   struct VertexHolder *excessList;
  103. } ShadowVolumeMemoryPool;
  104.  
  105. typedef struct _ShadowVolumeState {
  106.   ShadowVolumeMemoryPool *pool;
  107.  
  108.   GLfloat shadowProjectionDistance;
  109.   GLfloat extentScale;
  110.  
  111.   /* Scratch variables used during GLU tessellator callbacks. */
  112.   int saveFirst;
  113.   GLfloat *firstVertex;
  114. } ShadowVolumeState;
  115.  
  116. ShadowVolumeState *svs;
  117.  
  118. static void CALLBACK
  119. begin(GLenum type, void *shadowVolumeState)
  120. {
  121.   ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState;
  122.  
  123.   assert(type == GL_LINE_LOOP);
  124.   if (renderBoundary) {
  125.     glBegin(type);
  126.   } else {
  127.     svs->saveFirst = 1;
  128.     glBegin(GL_TRIANGLE_FAN);
  129.     glColor3f(0, 1, 0);
  130.     glVertex3f(0.0, 0.0, 0.0);
  131.   }
  132. }
  133.  
  134. static void CALLBACK
  135. vertex(void *data, void *shadowVolumeState)
  136. {
  137.   ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState;
  138.   GLfloat *v = data;
  139.  
  140.   if (renderBoundary) {
  141.     glVertex2fv(v);
  142.   } else {
  143.     if (svs->saveFirst) {
  144.       svs->firstVertex = v;
  145.       svs->saveFirst = 0;
  146.     }
  147.     glColor3f(0, 0, 1);
  148.     glVertex3f(svs->extentScale * v[X], svs->extentScale * v[Y],
  149.       svs->shadowProjectionDistance);
  150.   }
  151. }
  152.  
  153. static void CALLBACK
  154. end(void *shadowVolumeState)
  155. {
  156.   ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState;
  157.  
  158.   if (!renderBoundary) {
  159.     glColor3f(0, 0, 1);
  160.     glVertex3f(svs->extentScale * svs->firstVertex[X], svs->extentScale * svs->firstVertex[Y],
  161.       svs->shadowProjectionDistance);
  162.   }
  163.   glEnd();
  164. }
  165.  
  166. static void
  167. freeExcessList(ShadowVolumeMemoryPool * pool)
  168. {
  169.   struct VertexHolder *holder, *next;
  170.  
  171.   holder = pool->excessList;
  172.   while (holder) {
  173.     next = holder->next;
  174.     free(holder);
  175.     holder = next;
  176.   }
  177.   pool->excessList = NULL;
  178. }
  179.  
  180. /* ARGSUSED1 */
  181. static void CALLBACK
  182. combine(GLdouble coords[3], void *d[4], GLfloat w[4], void **dataOut, void *shadowVolumeState)
  183. {
  184.   ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState;
  185.   ShadowVolumeMemoryPool *pool = svs->pool;
  186.   struct VertexHolder *holder;
  187.   GLfloat *newCoords;
  188.  
  189.   if (pool->combineNext >= pool->combineListSize) {
  190.     holder = (struct VertexHolder *) malloc(sizeof(struct VertexHolder));
  191.     holder->next = pool->excessList;
  192.     pool->excessList = holder;
  193.     newCoords = holder->v;
  194.   } else {
  195.     newCoords = &pool->combineList[pool->combineNext * 2];
  196.   }
  197.  
  198.   newCoords[0] = coords[0];
  199.   newCoords[1] = coords[1];
  200.   *dataOut = newCoords;
  201.  
  202.   pool->combineNext++;
  203. }
  204.  
  205. static void CALLBACK
  206. error(GLenum errno)
  207. {
  208.   printf("ERROR: %s\n", gluErrorString(errno));
  209. }
  210.  
  211. static void
  212. processFeedback(GLint size, GLfloat * buffer, ShadowVolumeState * svs)
  213. {
  214.   ShadowVolumeMemoryPool *pool = svs->pool;
  215.   GLfloat *loc, *end, *eyeLoc;
  216.   GLdouble v[3];
  217.   int token, nvertices, i;
  218.   GLfloat passThroughToken;
  219.   int watchingForEyePos;
  220.  
  221.   if (pool->combineNext > pool->combineListSize) {
  222.     freeExcessList(pool);
  223.     pool->combineListSize = pool->combineNext;
  224.     pool->combineList = realloc(pool->combineList, sizeof(GLfloat) * 2 * pool->combineListSize);
  225.   }
  226.   pool->combineNext = 0;
  227.  
  228.   watchingForEyePos = 0;
  229.   eyeLoc = NULL;
  230.  
  231.   glColor3f(1, 1, 1);
  232.   gluTessBeginPolygon(pool->tess, svs);
  233.   loc = buffer;
  234.   end = buffer + size;
  235.   while (loc < end) {
  236.     token = *loc;
  237.     loc++;
  238.     switch (token) {
  239.     case GL_POLYGON_TOKEN:
  240.       nvertices = *loc;
  241.       loc++;
  242.       assert(nvertices >= 3);
  243.       gluTessBeginContour(pool->tess);
  244.       for (i = 0; i < nvertices; i++) {
  245.         v[0] = loc[0];
  246.         v[1] = loc[1];
  247.         v[2] = 0.0;
  248.         gluTessVertex(pool->tess, v, loc);
  249.         loc += 2;
  250.       }
  251.       gluTessEndContour(pool->tess);
  252.       break;
  253.     case GL_PASS_THROUGH_TOKEN:
  254.       passThroughToken = *loc;
  255.       if (passThroughToken == uniquePassThroughValue) {
  256.         watchingForEyePos = !watchingForEyePos;
  257.       } else {
  258.         /* Ignore everything else. */
  259.         fprintf(stderr, "ERROR: Unexpected feedback token 0x%x (%d).\n", token, token);
  260.       }
  261.       loc++;
  262.       break;
  263.     case GL_POINT_TOKEN:
  264.       if (watchingForEyePos) {
  265.         fprintf(stderr, "WARNING: Eye point possibly within the shadow volume.\n");
  266.         fprintf(stderr, "         Program should be improved to handle this.\n");
  267.         /* XXX Write code to handle this case.  You would need to determine
  268.            if the point was instead any of the returned boundary polyons.
  269.            Once you found that you were really in the clipping volume, then I
  270.            haven't quite thought about what you do. */
  271.         eyeLoc = loc;
  272.         watchingForEyePos = 0;
  273.       } else {
  274.         /* Ignore everything else. */
  275.         fprintf(stderr, "ERROR: Unexpected feedback token 0x%x (%d).\n",
  276.           token, token);
  277.       }
  278.       loc += 2;
  279.       break;
  280.     default:
  281.       /* Ignore everything else. */
  282.       fprintf(stderr, "ERROR: Unexpected feedback token 0x%x (%d).\n",
  283.         token, token);
  284.     }
  285.   }
  286.   gluTessEndPolygon(pool->tess);
  287.  
  288.   if (eyeLoc && renderBoundary) {
  289.     glColor3f(0, 1, 0);
  290.     glPointSize(7.0);
  291.     glBegin(GL_POINTS);
  292.     glVertex2fv(eyeLoc);
  293.     glEnd();
  294.   }
  295. }
  296.  
  297. /* Three element vector dot product. */
  298. static GLfloat
  299. vdot(const GLfloat * v1, const GLfloat * v2)
  300. {
  301.   return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
  302. }
  303.  
  304. /* Three element vector cross product. */
  305. static void
  306. vcross(const GLfloat * v1, const GLfloat * v2, GLfloat * cross)
  307. {
  308.   assert(v1 != cross && v2 != cross);
  309.   cross[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
  310.   cross[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
  311.   cross[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
  312. }
  313.  
  314. void
  315. svsFreeShadowVolumeState(ShadowVolumeState * svs)
  316. {
  317.   if (svs->pool) {
  318.     svs->pool->refcnt--;
  319.     if (svs->pool->refcnt == 0) {
  320.       if (svs->pool->excessList) {
  321.         freeExcessList(svs->pool);
  322.       }
  323.       if (svs->pool->combineList) {
  324.         free(svs->pool->combineList);
  325.       }
  326.       if (svs->pool->tess) {
  327.         gluDeleteTess(svs->pool->tess);
  328.       }
  329.       free(svs->pool);
  330.     }
  331.   }
  332.   free(svs);
  333. }
  334.  
  335. ShadowVolumeState *
  336. svsCreateShadowVolumeState(GLfloat shadowProjectionDistance,
  337.   ShadowVolumeState * shareSVS)
  338. {
  339.   ShadowVolumeState *svs;
  340.   ShadowVolumeMemoryPool *pool;
  341.   GLUtesselator *tess;
  342.  
  343.   svs = (ShadowVolumeState *) malloc(sizeof(ShadowVolumeState));
  344.   if (svs == NULL) {
  345.     return NULL;
  346.   }
  347.   svs->pool = NULL;
  348.  
  349.   if (shareSVS == NULL) {
  350.     pool = (ShadowVolumeMemoryPool *) malloc(sizeof(ShadowVolumeMemoryPool));
  351.     if (pool == NULL) {
  352.       svsFreeShadowVolumeState(svs);
  353.       return NULL;
  354.     }
  355.     pool->refcnt = 1;
  356.     pool->excessList = NULL;
  357.     pool->combineList = NULL;
  358.     pool->combineListSize = 0;
  359.     pool->combineNext = 0;
  360.     pool->tess = NULL;
  361.     svs->pool = pool;
  362.  
  363.     tess = gluNewTess();
  364.     if (tess == NULL) {
  365.       svsFreeShadowVolumeState(svs);
  366.       return NULL;
  367.     }
  368.     gluTessProperty(tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE);
  369.     gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
  370.     gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (void (CALLBACK*)()) begin);
  371.     gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (void (CALLBACK*)()) vertex);
  372.     gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (void (CALLBACK*)()) combine);
  373.     gluTessCallback(tess, GLU_TESS_END_DATA, (void (CALLBACK*)()) end);
  374.     gluTessCallback(tess, GLU_TESS_ERROR, (void (CALLBACK*)()) error);
  375.     pool->tess = tess;
  376.   } else {
  377.     pool = shareSVS->pool;
  378.     pool->refcnt++;
  379.   }
  380.  
  381.   svs->pool = pool;
  382.   svs->shadowProjectionDistance = shadowProjectionDistance;
  383.  
  384.   return svs;
  385. }
  386.  
  387. int
  388. svsGenerateShadowVolume(ShadowVolumeState * svs,
  389.   void (*renderFunc) (void), int feedbackBufferSizeGuess,
  390.   GLfloat maxRadius,
  391.   GLfloat lightPos[3], GLfloat objectPos[3], GLfloat eyePos[3])
  392. {
  393.   static GLfloat unit[3] =
  394.   {0.0, 0.0, 1.0};
  395.   static GLfloat *feedbackBuffer = NULL;
  396.   static int bufferSize = 0;
  397.   GLfloat axis[3], lightDelta[3], eyeDelta[3];
  398.   GLfloat nnear, ffar;  /* Avoid Intel C keywords.  Grumble. */
  399.   GLfloat lightDistance, eyeDistance, angle, fieldOfViewRatio, fieldOfViewAngle,
  400.     topScale, viewScale;
  401.   GLint returned;
  402.  
  403.   if (svs->pool->viewScale == 0.0) {
  404.     GLfloat maxViewSize[2];
  405.  
  406.     glGetFloatv(GL_MAX_VIEWPORT_DIMS, maxViewSize);
  407.     printf("max viewport = %gx%g\n", maxViewSize[0], maxViewSize[1]);
  408.     svs->pool->viewScale = SmallerOf(maxViewSize[0], maxViewSize[1]) / 2.0;
  409.   }
  410.   viewScale = svs->pool->viewScale;
  411.  
  412.   if (bufferSize > feedbackBufferSizeGuess) {
  413.     feedbackBufferSizeGuess = bufferSize;
  414.   }
  415.   /* Calculate the light's distance from the object being shadowed. */
  416.   lightDelta[X] = objectPos[X] - lightPos[X];
  417.   lightDelta[Y] = objectPos[Y] - lightPos[Y];
  418.   lightDelta[Z] = objectPos[Z] - lightPos[Z];
  419.   lightDistance = sqrt(lightDelta[X] * lightDelta[X] +
  420.     lightDelta[Y] * lightDelta[Y] + lightDelta[Z] * lightDelta[Z]);
  421.  
  422.   /* Determine the appropriate field of view.  We want to use as narrow a
  423.      field of view as possible to not waste resolution, but not narrower than
  424.      the object.  Add 50% extra slop. */
  425.   fieldOfViewRatio = maxRadius / lightDistance;
  426.   if (fieldOfViewRatio > 0.99) {
  427.     fprintf(stderr, "WARNING: Clamping FOV to 164 degrees for determining shadow boundary.\n");
  428.     fprintf(stderr, "         Light distance = %g, object maxmium radius = %g\n",
  429.       lightDistance, maxRadius);
  430.  
  431.     /* 2*asin(0.99) ~= 164 degrees. */
  432.     fieldOfViewRatio = 0.99;
  433.   }
  434.   /* Pre-compute scaling factors for the near and far extent of the shadow
  435.      volume. */
  436.   svs->extentScale = svs->shadowProjectionDistance * fieldOfViewRatio / viewScale;
  437.  
  438.   glMatrixMode(GL_PROJECTION);
  439.   glPushMatrix();
  440.   glLoadIdentity();
  441.  
  442.   nnear = 0.5 * (lightDistance - maxRadius);
  443.   if (nnear < 0.0001) {
  444.     fprintf(stderr, "WARNING: Clamping near clip plane to 0.0001 because light source too near.\n");
  445.     fprintf(stderr, "         Light distance = %g, object maxmium radius = %g\n",
  446.       lightDistance, maxRadius);
  447.     nnear = 0.0001;
  448.   }
  449.   ffar = 2.0 * (lightDistance + maxRadius);
  450.   if (eyePos) {
  451.     eyeDelta[X] = eyePos[X] - lightPos[X];
  452.     eyeDelta[Y] = eyePos[Y] - lightPos[Y];
  453.     eyeDelta[Z] = eyePos[Z] - lightPos[Z];
  454.     eyeDistance = 1.05 * sqrt(eyeDelta[X] * eyeDelta[X] + eyeDelta[Y] * eyeDelta[Y] + eyeDelta[Z] * eyeDelta[Z]);
  455.     if (eyeDistance > ffar) {
  456.       ffar = eyeDistance;
  457.     }
  458.   }
  459.   fieldOfViewAngle = 2.0 * asin(fieldOfViewRatio) * 180 / M_PI;
  460.   gluPerspective(fieldOfViewAngle, 1.0, nnear, ffar);
  461.  
  462.   glMatrixMode(GL_MODELVIEW);
  463.   glPushMatrix();
  464.   glLoadIdentity();
  465.   /* XXX Need to update "up vector".  Degenerate when light directly above or 
  466.      below the object. */
  467.   gluLookAt(lightPos[X], lightPos[Y], lightPos[Z],
  468.     objectPos[X], objectPos[Y], objectPos[Z],
  469.     0.0, 1.0, 0.0);     /* up is in positive Y direction */
  470.  
  471.   glPushAttrib(GL_VIEWPORT_BIT);
  472.   glViewport(-viewScale, -viewScale, 2 * viewScale, 2 * viewScale);
  473.  
  474. doFeedback:
  475.  
  476.   /* XXX Careful, some systems still don't understand realloc of NULL. */
  477.   if (bufferSize < feedbackBufferSizeGuess) {
  478.     bufferSize = feedbackBufferSizeGuess;
  479.     /* XXX Add 32 words of slop (an extra cache line) to end for buggy
  480.        hardware that uses DMA to return feedback results but that sometimes
  481.        overrun the buffer.  Yuck. */
  482.     feedbackBuffer = realloc(feedbackBuffer, bufferSize * sizeof(GLfloat) + 32 * 4);
  483.   }
  484.   glFeedbackBuffer(bufferSize, GL_2D, feedbackBuffer);
  485.  
  486.   (void) glRenderMode(GL_FEEDBACK);
  487.  
  488.   (*renderFunc) ();
  489.  
  490.   /* Render the eye position.  The eye position is "bracketed" by unique pass 
  491.      through tokens.  These bracketing pass through tokens let us determine
  492.      if the eye position was clipped or not.  This helps us determine whether 
  493.      the eye position is possibly within the shadow volume or not.  If the
  494.      point is clipped, the eye position is not in the shadow volume.  If the
  495.      point is not clipped, a more complicated test is necessary to determine
  496.      if the eye position is really in the shadow volume or not.  See
  497.      processFeedback. */
  498.   if (eyePos) {
  499.     glPassThrough(uniquePassThroughValue);
  500.     glBegin(GL_POINTS);
  501.     glVertex3fv(eyePos);
  502.     glEnd();
  503.     glPassThrough(uniquePassThroughValue);
  504.   }
  505.   returned = glRenderMode(GL_RENDER);
  506. #if 0
  507.   if (returned == -1) {
  508. #else
  509.   /* XXX RealityEngine workaround. */
  510.   if (returned == -1 || returned == feedbackBufferSizeGuess) {
  511. #endif
  512.     feedbackBufferSizeGuess = feedbackBufferSizeGuess + (feedbackBufferSizeGuess >> 1);
  513.     goto doFeedback;    /* Try again with larger feedback buffer. */
  514.   }
  515.   glMatrixMode(GL_PROJECTION);
  516.   glPopMatrix();
  517.   glMatrixMode(GL_MODELVIEW);
  518.   glPopMatrix();
  519.   glPopAttrib();        /* Restore viewport. */
  520.  
  521.   if (renderBoundary) {
  522.     glMatrixMode(GL_PROJECTION);
  523.     glLoadIdentity();
  524.     gluOrtho2D(-viewScale, viewScale, -viewScale, viewScale);
  525.     glMatrixMode(GL_MODELVIEW);
  526.     glLoadIdentity();
  527.     processFeedback(returned, feedbackBuffer, svs);
  528.   } else {
  529.     glNewList(DL_BASE_SHADOW_VOLUME, GL_COMPILE);
  530.     vcross(unit, lightDelta, axis);
  531.     angle = acos(vdot(unit, lightDelta) / lightDistance) * 180.0 / M_PI;
  532.     glRotatef(angle, axis[X], axis[Y], axis[Z]);
  533.     processFeedback(returned, feedbackBuffer, svs);
  534.     glEndList();
  535.  
  536.     glNewList(DL_SHADOW_VOLUME, GL_COMPILE);
  537.     glPushMatrix();
  538.     glTranslatef(lightPos[X], lightPos[Y], lightPos[Z]);
  539.     glCallList(DL_BASE_SHADOW_VOLUME);
  540.     glPopMatrix();
  541.     glEndList();
  542.  
  543.     glNewList(DL_SHADOW_VOLUME_TOP, GL_COMPILE);
  544.     glPushMatrix();
  545.     glTranslatef(lightPos[X], lightPos[Y], lightPos[Z]);
  546.     topScale = (lightDistance + maxRadius) / svs->shadowProjectionDistance;
  547.     glScalef(topScale, topScale, topScale);
  548.     glCallList(DL_BASE_SHADOW_VOLUME);
  549.     glPopMatrix();
  550.     glEndList();
  551.   }
  552.   return returned;
  553. }
  554.  
  555. GLfloat objectMaxRadius[] =
  556. {
  557.   8.0 + 2.0,            /* M_TORUS */
  558.   12.0 / 2.0 * 1.142,   /* M_CUBE */
  559.   8.0,                  /* M_SPHERE */
  560.   8.0,                  /* M_ICO */
  561.   8.0 + 2.0,            /* M_DOUBLE_TORUS */
  562. };
  563.  
  564. void
  565. renderShadowingObject(void)
  566. {
  567.   static int torusList = 0, cubeList = 0, sphereList = 0, icoList = 0;
  568.  
  569.   glPushMatrix();
  570.   glTranslatef(objectPos[X], objectPos[Y], objectPos[Z]);
  571.   glRotatef(angle, 1.0, 0.3, 0.0);
  572.   switch (shape) {
  573.   case M_TORUS:
  574.     if (torusList) {
  575.       glCallList(torusList);
  576.     } else {
  577.       torusList = DL_TORUS;
  578.       glNewList(torusList, GL_COMPILE_AND_EXECUTE);
  579.       glutSolidTorus(2.0, 8.0, 8, 15);
  580.       glEndList();
  581.     }
  582.     break;
  583.   case M_CUBE:
  584.     if (cubeList) {
  585.       glCallList(cubeList);
  586.     } else {
  587.       cubeList = DL_CUBE;
  588.       glNewList(cubeList, GL_COMPILE_AND_EXECUTE);
  589.       glutSolidCube(12.0);
  590.       glEndList();
  591.     }
  592.     break;
  593.   case M_SPHERE:
  594.     if (sphereList) {
  595.       glCallList(sphereList);
  596.     } else {
  597.       sphereList = DL_SPHERE;
  598.       glNewList(sphereList, GL_COMPILE_AND_EXECUTE);
  599.       glutSolidSphere(8.0, 10, 10);
  600.       glEndList();
  601.     }
  602.     break;
  603.   case M_ICO:
  604.     if (icoList) {
  605.       glCallList(icoList);
  606.     } else {
  607.       icoList = DL_ICO;
  608.       glNewList(icoList, GL_COMPILE_AND_EXECUTE);
  609.       glEnable(GL_NORMALIZE);
  610.       glPushMatrix();
  611.       glScalef(8.0, 8.0, 8.0);
  612.       glutSolidIcosahedron();
  613.       glPopMatrix();
  614.       glDisable(GL_NORMALIZE);
  615.       glEndList();
  616.     }
  617.     break;
  618.   case M_DOUBLE_TORUS:
  619.     if (torusList) {
  620.       glCallList(torusList);
  621.     } else {
  622.       torusList = DL_TORUS;
  623.       glNewList(torusList, GL_COMPILE_AND_EXECUTE);
  624.       glutSolidTorus(2.0, 8.0, 8, 15);
  625.       glEndList();
  626.     }
  627.     glRotatef(90, 0, 1, 0);
  628.     glCallList(torusList);
  629.     break;
  630.   }
  631.   glPopMatrix();
  632. }
  633.  
  634. void
  635. sphere(void)
  636. {
  637.   glPushMatrix();
  638.   glTranslatef(60.0, -50.0, -400.0);
  639.   glCallList(DL_BALL);
  640.   glPopMatrix();
  641. }
  642.  
  643. void
  644. cone(void)
  645. {
  646.   glPushMatrix();
  647.   glTranslatef(-40.0, -40.0, -400.0);
  648.   glCallList(DL_CONE);
  649.   glPopMatrix();
  650. }
  651.  
  652. void
  653. scene(void)
  654. {
  655.   /* material properties for objects in scene */
  656.   static GLfloat wall_mat[] =
  657.   {1.0, 1.0, 1.0, 1.0};
  658.   static GLfloat shad_mat[] =
  659.   {1.0, 0.1, 0.1, 1.0};
  660.  
  661.   glMatrixMode(GL_PROJECTION);
  662.   glLoadIdentity();
  663.   if (view == M_LIGHT_SOURCE_VIEW) {
  664.     gluPerspective(45.0, 1.0, 0.5, 600.0);
  665.   } else {
  666.     gluPerspective(33.0, 1.0, 10.0, 600.0);
  667.   }
  668.   glMatrixMode(GL_MODELVIEW);
  669.   glLoadIdentity();
  670.   if (view == M_LIGHT_SOURCE_VIEW) {
  671.     gluLookAt(lightPos[X], lightPos[Y], lightPos[Z],
  672.       objectPos[X], objectPos[Y], objectPos[Z],
  673.       0.0, 1.0, 0.);    /* up is in positive Y direction */
  674.   } else {
  675.     gluLookAt(0.0, 0.0, 0.0,
  676.       0.0, 0.0, -100.0,
  677.       0.0, 1.0, 0.);    /* up is in positive Y direction */
  678.   }
  679.   /* Place light 0 in the right place. */
  680.   glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
  681.  
  682.   /* Note: wall verticies are ordered so they are all front facing this lets
  683.      me do back face culling to speed things up.  */
  684.  
  685.   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, wall_mat);
  686.  
  687.   /* Floor with checkerboard texture.  */
  688.   glEnable(GL_TEXTURE_2D);
  689.  
  690.   glColor3f(0, 0, 0);
  691.  
  692.   /* Since we want to turn texturing on for floor only, we have to make floor
  693.      a separate glBegin()/glEnd() sequence. You can't turn texturing on and
  694.      off between begin and end calls */
  695.   glBegin(GL_QUADS);
  696.   glNormal3f(0.0, 1.0, 0.0);
  697.   glTexCoord2i(0, 0);
  698.   glVertex3f(-100.0, -100.0, -320.0);
  699.   glTexCoord2i(4, 0);
  700.   glVertex3f(100.0, -100.0, -320.0);
  701.   glTexCoord2i(4, 4);
  702.   glVertex3f(100.0, -100.0, -520.0);
  703.   glTexCoord2i(0, 4);
  704.   glVertex3f(-100.0, -100.0, -520.0);
  705.   glEnd();
  706.  
  707.   glDisable(GL_TEXTURE_2D);
  708.  
  709.   /* Walls. */
  710.  
  711.   glBegin(GL_QUADS);
  712.   /* Left wall. */
  713.   glNormal3f(1.0, 0.0, 0.0);
  714.   glVertex3f(-100.0, -100.0, -320.0);
  715.   glVertex3f(-100.0, -100.0, -520.0);
  716.   glVertex3f(-100.0, 100.0, -520.0);
  717.   glVertex3f(-100.0, 100.0, -320.0);
  718.  
  719.   /* Right wall. */
  720.   glNormal3f(-1.0, 0.0, 0.0);
  721.   glVertex3f(100.0, -100.0, -320.0);
  722.   glVertex3f(100.0, 100.0, -320.0);
  723.   glVertex3f(100.0, 100.0, -520.0);
  724.   glVertex3f(100.0, -100.0, -520.0);
  725.  
  726.   /* Ceiling. */
  727.   glNormal3f(0.0, -1.0, 0.0);
  728.   glVertex3f(-100.0, 100.0, -320.0);
  729.   glVertex3f(-100.0, 100.0, -520.0);
  730.   glVertex3f(100.0, 100.0, -520.0);
  731.   glVertex3f(100.0, 100.0, -320.0);
  732.  
  733.   /* Back wall. */
  734.   glNormal3f(0.0, 0.0, 1.0);
  735.   glVertex3f(-100.0, -100.0, -520.0);
  736.   glVertex3f(100.0, -100.0, -520.0);
  737.   glVertex3f(100.0, 100.0, -520.0);
  738.   glVertex3f(-100.0, 100.0, -520.0);
  739.   glEnd();
  740.  
  741.   cone();
  742.  
  743.   sphere();
  744.  
  745.   glPushMatrix();
  746.   glTranslatef(lightPos[X], lightPos[Y], lightPos[Z]);
  747.   glCallList(DL_LIGHT);
  748.   glPopMatrix();
  749.  
  750.   /* Draw shadowing object. */
  751.   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, shad_mat);
  752.  
  753.   renderShadowingObject();
  754. }
  755.  
  756. void
  757. generateShadowVolume(void)
  758. {
  759.   GLfloat *eyePos;
  760.  
  761.   if (view == M_LIGHT_SOURCE_VIEW) {
  762.     eyePos = lightPos;
  763.   } else {
  764.     eyePos = sceneEyePos;
  765.   }
  766.   /* XXX The 2048 feedbackBufferGuessSize is large enough to
  767.      workaround the Octane/Impact bug where if the feedback
  768.      buffer is under 2048 entries, a buggy hardware feedback
  769.      path is used.  2048 forces the (bug free) software path.
  770.      This bug is fixed in IRIX 6.5. */
  771.   svsGenerateShadowVolume(svs, renderShadowingObject, 2048, maxRadius,
  772.     lightPos, objectPos, eyePos);
  773. }
  774.  
  775. void
  776. display(void)
  777. {
  778.   if (renderBoundary) {
  779.     glClear(GL_COLOR_BUFFER_BIT);
  780.     glDisable(GL_LIGHTING);
  781.     glDisable(GL_DEPTH_TEST);
  782.     generateShadowVolume();
  783.     glEnable(GL_LIGHTING);
  784.   } else {
  785.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  786.     switch (renderMode) {
  787.     case M_NO_SHADOW:
  788.       glEnable(GL_DEPTH_TEST);
  789.       glEnable(GL_LIGHT0);
  790.       scene();
  791.       break;
  792.     case M_NO_LIGHT:
  793.       /* Render scene without the light source enabled (conceptually, the
  794.          entire scene is "in the shadow"). */
  795.       glEnable(GL_DEPTH_TEST);
  796.       glDisable(GL_LIGHT0);
  797.       scene();
  798.       break;
  799.     case M_FRONT_VOLUME:
  800.     case M_BACK_VOLUME:
  801.       generateShadowVolume();
  802.  
  803.       glEnable(GL_DEPTH_TEST);
  804.       glEnable(GL_LIGHT0);
  805.       scene();
  806.       if (frontFace) {
  807.         glFrontFace(GL_CW);
  808.       } else {
  809.         glFrontFace(GL_CCW);
  810.       }
  811.       glCallList(DL_SHADOW_VOLUME);
  812.       glFrontFace(GL_CCW);
  813.       break;
  814.     case M_SHADOW:
  815.       /* Construct DL_SHADOW_VOLUME display list for the scene's current
  816.          shadow volume. */
  817.       generateShadowVolume();
  818.  
  819.       /* 1st scene pass. */
  820.       glEnable(GL_DEPTH_TEST);
  821.       glEnable(GL_LIGHT0);
  822.       scene();
  823.  
  824.       /* 1st shadow volume pass:  Enable stencil to increment the stencil
  825.          value of pixels that pass the depth test when drawing the front
  826.          facing polygons of the shadow volume.  Do not update the depth
  827.          buffer while rendering the shadow volume. */
  828.       glDisable(GL_LIGHTING);
  829.       glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  830.       glEnable(GL_STENCIL_TEST);
  831.       glDepthMask(GL_FALSE);
  832.       glStencilFunc(GL_ALWAYS, 0, 0);
  833.       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
  834.       glCullFace(GL_FRONT);
  835.       glCallList(DL_SHADOW_VOLUME);
  836.  
  837.       /* 2nd shadow volume pass:  Now, draw the back facing polygons of the
  838.          shadow volume except decrement pixels that pass the depth test.
  839.          Again, do not update the depth buffer. */
  840.       glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
  841.       glCullFace(GL_BACK);
  842.       glCallList(DL_SHADOW_VOLUME);
  843.  
  844.       glDisable(GL_CULL_FACE);
  845.       glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
  846.       glCallList(DL_SHADOW_VOLUME_TOP);
  847.       glEnable(GL_CULL_FACE);
  848.  
  849.       /* Now, pixels that lie within the shadow volume are tagged with a one
  850.          stencil value.  Empty shadowed regions of the shadow volume get
  851.          incremented, then decremented, to resolve to a net zero stencil
  852.          value. */
  853.  
  854.       /* 2nd scene pass (render shadowed region):  Re-enable update of the
  855.          depth and color buffer (use GL_LEQUAL for depth buffer so we can
  856.          over-write depth values again with color.  Switch back to backface
  857.          culling and disable the light source.  Only update pixels with a
  858.          stencil value of one (shadowed). */
  859.       glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  860. #if 0
  861.       glDepthFunc(GL_EQUAL);
  862. #else
  863.       glDepthMask(GL_TRUE);
  864.       glDepthFunc(GL_LEQUAL);
  865. #endif
  866.       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
  867.       glStencilFunc(GL_EQUAL, 1, 1);
  868.       glDisable(GL_LIGHT0);
  869.       glEnable(GL_LIGHTING);
  870.       scene();
  871.  
  872.       /* Put state back to sane modes. */
  873.       glDepthMask(GL_TRUE);
  874.       glDepthFunc(GL_LESS);
  875.       glDisable(GL_STENCIL_TEST);
  876.       break;
  877.     }
  878.   }
  879.   glutSwapBuffers();
  880. }
  881.  
  882. void
  883. idle(void)
  884. {
  885.   if (rotatingObject) {
  886.     angle += 10.0;
  887.   }
  888.   if (swingingLight) {
  889.     swingTime += 0.05;
  890.     lightPos[X] += 2 * cos(swingTime);
  891.   }
  892.   glutPostRedisplay();
  893. }
  894.  
  895. void
  896. menu(int value)
  897. {
  898.   switch (value) {
  899.   case M_TORUS:
  900.   case M_CUBE:
  901.   case M_SPHERE:
  902.   case M_ICO:
  903.   case M_DOUBLE_TORUS:
  904.     shape = value;
  905.     maxRadius = objectMaxRadius[value];
  906.     glutPostRedisplay();
  907.     break;
  908.   case M_ANGLE:
  909.     angle += 10.0;
  910.     glutPostRedisplay();
  911.     break;
  912.   case M_BOUNDARY:
  913.     renderBoundary = 1;
  914.     glutPostRedisplay();
  915.     break;
  916.   case M_FRONT_VOLUME:
  917.   case M_BACK_VOLUME:
  918.     frontFace = (value == M_FRONT_VOLUME);
  919.     /* FALLTHROUGH */
  920.   case M_NO_SHADOW:
  921.   case M_NO_LIGHT:
  922.   case M_SHADOW:
  923.     renderBoundary = 0;
  924.     renderMode = value;
  925.     glutPostRedisplay();
  926.     break;
  927.   case M_LIGHT_SOURCE_VIEW:
  928.   case M_NORMAL_VIEW:
  929.     view = value;
  930.     glutPostRedisplay();
  931.     break;
  932.   case M_STOP:
  933.     swingingLight = 0;
  934.     rotatingObject = 0;
  935.     glutIdleFunc(NULL);
  936.     break;
  937.   case M_SPIN:
  938.     rotatingObject = 1;
  939.     glutIdleFunc(idle);
  940.     break;
  941.   case M_SWING:
  942.     swingingLight = 1;
  943.     glutIdleFunc(idle);
  944.     break;
  945.   case 666:
  946.     svsFreeShadowVolumeState(svs);
  947.     exit(0);
  948.     /* NOTREACHED */
  949.     break;
  950.   }
  951. }
  952.  
  953. void
  954. visible(int vis)
  955. {
  956.   if (vis == GLUT_VISIBLE && swingingLight && rotatingObject) {
  957.     glutIdleFunc(idle);
  958.   } else {
  959.     glutIdleFunc(NULL);
  960.   }
  961. }
  962.  
  963. /* ARGSUSED1 */
  964. void
  965. key(unsigned char c, int x, int y)
  966. {
  967.   switch (c) {
  968.   case 27:             /* Escape. */
  969.     svsFreeShadowVolumeState(svs);
  970.     exit(0);
  971.     break;
  972.   case 13:             /* Return. */
  973.     swingingLight = !swingingLight;
  974.     swingTime = M_PI / 2.0;
  975.     break;
  976.   case ' ':            /* Space. */
  977.     if (rotatingObject || swingingLight) {
  978.       rotatingObject = 0;
  979.       swingingLight = 0;
  980.       glutIdleFunc(NULL);
  981.     } else {
  982.       rotatingObject = 1;
  983.       swingingLight = 1;
  984.       glutIdleFunc(idle);
  985.     }
  986.     break;
  987.   }
  988. }
  989.  
  990. /* ARGSUSED1 */
  991. void
  992. special(int key, int x, int y)
  993. {
  994.   switch (key) {
  995.   case GLUT_KEY_HOME:
  996.     frontFace = !frontFace;
  997.     glutPostRedisplay();
  998.     break;
  999.   case GLUT_KEY_UP:
  1000.     lightPos[Y] += 10.0;
  1001.     glutPostRedisplay();
  1002.     break;
  1003.   case GLUT_KEY_DOWN:
  1004.     lightPos[Y] -= 10.0;
  1005.     glutPostRedisplay();
  1006.     break;
  1007.   case GLUT_KEY_PAGE_UP:
  1008.     lightPos[Z] += 10.0;
  1009.     glutPostRedisplay();
  1010.     break;
  1011.   case GLUT_KEY_PAGE_DOWN:
  1012.     lightPos[Z] -= 10.0;
  1013.     glutPostRedisplay();
  1014.     break;
  1015.   case GLUT_KEY_RIGHT:
  1016.     lightPos[X] += 10.0;
  1017.     glutPostRedisplay();
  1018.     break;
  1019.   case GLUT_KEY_LEFT:
  1020.     lightPos[X] -= 10.0;
  1021.     glutPostRedisplay();
  1022.     break;
  1023.   }
  1024. }
  1025.  
  1026. /* Create a single component checkboard texture map. */
  1027. GLfloat *
  1028. makeTexture(int maxs, int maxt)
  1029. {
  1030.   int s, t;
  1031.   static GLfloat *texture;
  1032.  
  1033.   texture = (GLfloat *) malloc(maxs * maxt * sizeof(GLfloat));
  1034.   for (t = 0; t < maxt; t++) {
  1035.     for (s = 0; s < maxs; s++) {
  1036.       texture[s + maxs * t] = ((s >> 4) & 0x1) ^ ((t >> 4) & 0x1);
  1037.     }
  1038.   }
  1039.   return texture;
  1040. }
  1041.  
  1042. void
  1043. initScene(void)
  1044. {
  1045.   GLfloat *tex;
  1046.   GLUquadricObj *qobj;
  1047.   static GLfloat sphere_mat[] =
  1048.   {1.0, 0.5, 0.0, 1.0};
  1049.   static GLfloat cone_mat[] =
  1050.   {0.0, 0.5, 1.0, 1.0};
  1051.  
  1052.   /* Turn on features. */
  1053.   glEnable(GL_DEPTH_TEST);
  1054.   glEnable(GL_LIGHTING);
  1055.   glEnable(GL_LIGHT0);
  1056.   glEnable(GL_CULL_FACE);
  1057.  
  1058.   /* remove back faces to speed things up */
  1059.   glCullFace(GL_BACK);
  1060.  
  1061.   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  1062.   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  1063.  
  1064.   /* Make display lists for sphere and cone; for efficiency. */
  1065.  
  1066.   qobj = gluNewQuadric();
  1067.  
  1068.   glNewList(DL_BALL, GL_COMPILE);
  1069.   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, sphere_mat);
  1070.   gluSphere(qobj, 20.0, 20, 20);
  1071.   glEndList();
  1072.  
  1073.   glNewList(DL_CONE, GL_COMPILE);
  1074.   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cone_mat);
  1075.   glRotatef(-90.0, 1.0, 0.0, 0.0);
  1076.   gluDisk(qobj, 0.0, 20.0, 20, 1);
  1077.   gluCylinder(qobj, 20.0, 0.0, 60.0, 20, 20);
  1078.   glEndList();
  1079.  
  1080.   glNewList(DL_LIGHT, GL_COMPILE);
  1081.   glDisable(GL_LIGHTING);
  1082.   glColor3f(0.9, 0.9, 0.6);
  1083.   gluSphere(qobj, 5.0, 20, 20);
  1084.   glEnable(GL_LIGHTING);
  1085.   glEndList();
  1086.  
  1087.   gluDeleteQuadric(qobj);
  1088.  
  1089.   /* load pattern for current 2d texture */
  1090.   tex = makeTexture(TEXDIM, TEXDIM);
  1091.   glTexImage2D(GL_TEXTURE_2D, 0, 1, TEXDIM, TEXDIM, 0, GL_RED, GL_FLOAT, tex);
  1092.   free(tex);
  1093. }
  1094.  
  1095. int
  1096. main(int argc, char **argv)
  1097. {
  1098.   int shapeMenu, viewMenu, actionMenu, renderModeMenu;
  1099.  
  1100.   svs = svsCreateShadowVolumeState(1000.0, NULL);
  1101.  
  1102.   glutInitDisplayString("stencil>=1 rgb double depth samples");
  1103.   glutInit(&argc, argv);
  1104.  
  1105.   glutCreateWindow("shadowfun");
  1106.  
  1107.   stencilBits = glutGet(GLUT_WINDOW_STENCIL_SIZE);
  1108.   printf("bits of stencil = %d\n", stencilBits);
  1109.  
  1110.   glutDisplayFunc(display);
  1111.   glutVisibilityFunc(visible);
  1112.   glutSpecialFunc(special);
  1113.   glutKeyboardFunc(key);
  1114.  
  1115.   initScene();
  1116.  
  1117.   shapeMenu = glutCreateMenu(menu);
  1118.   glutAddMenuEntry("Torus", M_TORUS);
  1119.   glutAddMenuEntry("Cube", M_CUBE);
  1120.   glutAddMenuEntry("Sphere", M_SPHERE);
  1121.   glutAddMenuEntry("Icosahedron", M_ICO);
  1122.   glutAddMenuEntry("Double Torus", M_DOUBLE_TORUS);
  1123.  
  1124.   viewMenu = glutCreateMenu(menu);
  1125.   glutAddMenuEntry("Normal view", M_NORMAL_VIEW);
  1126.   glutAddMenuEntry("Light source view", M_LIGHT_SOURCE_VIEW);
  1127.  
  1128.   renderModeMenu = glutCreateMenu(menu);
  1129.   glutAddMenuEntry("With shadow", M_SHADOW);
  1130.   glutAddMenuEntry("With front shadow volume", M_FRONT_VOLUME);
  1131.   glutAddMenuEntry("With back shadow volume", M_BACK_VOLUME);
  1132.   glutAddMenuEntry("Without shadow", M_NO_SHADOW);
  1133.   glutAddMenuEntry("Without light", M_NO_LIGHT);
  1134.   glutAddMenuEntry("2D shadow boundary", M_BOUNDARY);
  1135.  
  1136.   actionMenu = glutCreateMenu(menu);
  1137.   glutAddMenuEntry("Spin object", M_SPIN);
  1138.   glutAddMenuEntry("Swing light", M_SWING);
  1139.   glutAddMenuEntry("Stop", M_STOP);
  1140.  
  1141.   glutCreateMenu(menu);
  1142.   glutAddSubMenu("Object shape", shapeMenu);
  1143.   glutAddSubMenu("Viewpoint", viewMenu);
  1144.   glutAddSubMenu("Render mode", renderModeMenu);
  1145.   glutAddSubMenu("Action", actionMenu);
  1146.   glutAddMenuEntry("Step rotate", M_ANGLE);
  1147.   glutAddMenuEntry("Quit", 666);
  1148.   glutAttachMenu(GLUT_RIGHT_BUTTON);
  1149.  
  1150.   menu(M_DOUBLE_TORUS);
  1151.  
  1152.   glutMainLoop();
  1153.   return 0;             /* ANSI C requires main to return int. */
  1154. }
  1155.  
  1156. #else
  1157. int main(int argc, char** argv)
  1158. {
  1159.   fprintf(stderr, "This program requires the new tesselator API in GLU 1.2.\n");
  1160.   fprintf(stderr, "Your GLU library does not support this new interface, sorry.\n");
  1161.   return 0;
  1162. }
  1163. #endif  /* GLU_VERSION_1_2 */
  1164.